/**
 * 
 * 这个的一个扩充的性能就是在没有addListener之前的事件保存，当addListern的时候再发出去~~~先不管这个，这个功能不动，现在只关心mp3简化
 * 
 * 注意我认为startLoad在启动的时候声音是0.7
 * 
 * 注意一个startLoad我认为在开始的时候是pause的，只有第一个字节加载近来了才去放
 * 
 * ------------
 * 这个没有严格测试过，要小心啊!
 * 
 * 注意初始化的条件，初始化的时候声音是0，默认是暂停的 
 * 
 * ~1105取消在这里做的超时处理
 * 
 *1114 	做好了seek,策略是pause着的时候就直接设置starPosi,playing的时候就pause,startPosi=seekTime,resum
 *
 *as2经验
 *很重要的用法，每次新的sound之前都要sd=new Sound(),sd.onLoad=function()定义一下
否则在sd.loadSound()频繁调用的时候sd.onLoad里的得到的是false,导致了问题.连续多次

 * startLoad();
 * resume()
 * pause()
 * resume()
 * startFadeout();
 * setVol();
 * close()
 * 
 * ----
 * !!需要具备的一个能力就是在没有listener的时候暂存事件，当有了listern的时候再统一发出
 * 这个是做为预先加载机制的时候很重要的东西~?,用起来比较的自然吧，不用再单独变成处理，按通用的统一的规则处理了
 */
package kgame5.kmedia.xplayer {
import kgame5.kutil.*;

import flash.events.*;
import flash.media.*;
import flash.net.*;
import flash.utils.*;

public class MP3Player extends XPlayer {
	//加载歌曲超时处理，单位是毫秒
	public static const TIMEROUT : int = 1 * 60 * 1000;
	//缓冲7秒
	public static const BUFFERDUR : int = 7 * 1000;
	
	//__________________Property___________________
	//消息队列缓存，这个是为了处理在addListern之前发声了事件而用的，当addListern之后就把他们房到队列里
	private var fileUrl : String = "";

	//--
	private var sd : Sound = null;
	private var sdc : SoundChannel = null;
	private var sdt : SoundTransform = null;

	//--一些标志
	//在声音完全加载近来之前用来计算总时间长度
	private var isLoadOK : Boolean = false;

	//是否发生错误，发生了错误则在声音淡出的时候不设置sn.setVolumn(),getVol()里返回是0
	//在每次start（）的ns.play()之前设置为false,发生了错误设置为true
	private var isError : Boolean = false;

	//是否被外部命令在播放，可能外部命令播放而我在处在未加载状态或者是缓冲状态
	private var isPlaying2 : Boolean = false;

	//播放完毕的时候sd.getVol,setVol里返回0,这个是为了处理在淡出中播放完毕
	private var isPlayComplete : Boolean = false;

	//--
	//当启动播放的时候应该在哪里播放
	private var startPosi : Number = 0;

	//这个用来存储别外部设置的声音
	private var outerVolume : Number = 0.7;

	//--跟服务器建立连接的时候的超时处理//过了一定时间还没从服务器加载到一个字节就认为加载失败
	private var timer_connecting : Timer;
	
	//--控制
	private var volFader : VolumeFader;

	//--累加的，用来个每个mp3Player一个编号
	private var id : int = 0;
	//累加的，用来给每一个xplayer实例一个自己的编号
	private static var ID : int = 0;

	//保存在addListenr之前的发生的事件
	private var aMsg : Array = null;
	private var aListener : Array = null;

	//__________________Method___________________
	public  function MP3Player(p_fileUrl : String) {
		fileUrl = p_fileUrl;
		id = ID++;
		
		trace2("new MP3Player");
		//--
		sd = new Sound();
		
		outerVolume = 0.7;
		sdt = new SoundTransform(outerVolume);
		//系统默认的是1
		
		//声音淡出器
		volFader = new VolumeFader(this);
		
		//保存在addLisener之前缓存的消息
		aMsg = new Array();
		//--保存自己的listener
		aListener = new Array();
	}

	//启动加载,已经执行过加载了就不会再执行了,一个重要的注意是开始start的时候是默认关闭的(因为还每调用sd.play呢)，需要调用resume()来播放
	private var isFirstRun : Boolean = true;

	override public function startLoad() : void {
		
		trace2("xplayer.startLoad " + isFirstRun);
		if(isFirstRun) {
			isFirstRun = false;
		}else {
			//这个是说start只执行一遍，因为start在外部和在OnLoadComplete()是都有可能执行,这个是资源池要用的
			//T.p("!已经执行过startLoad了");
			return;
		}
		
		//--
		isError = false;
		isPlayComplete = false;
		isPlaying2 = false;
		isLoadOK = false;
		
		//--
		if(checkFileUrl(fileUrl)) {
			//--
			addListener_SD();
		    
			//--
			trace2("sd.真正开始load");
			var req : URLRequest = new URLRequest(this.fileUrl);
			var context : SoundLoaderContext = new SoundLoaderContext(BUFFERDUR, false);
			//7秒
			sd.load(req, context);
		   
			//--超时处理
			timer_connecting = new Timer(TIMEROUT);
			timer_connecting.addEventListener(TimerEvent.TIMER, timerHandler_connecting);
			timer_connecting.start();//开始记时
		}else {
			//直接判断文件无效ok
			//此时不需要释放什么资源也，因为刚new的时候没有什么资源呢还
			trace2("xplayer.startLoad 可以提前判断无此mp3文件,可以马上通知加载失败");
			
			this.notifyPlayerLoadResult(false);
		}
	}

	private function timerHandler_connecting(e : TimerEvent) {
		//通知加载失败
		trace2("mp3xPlayer定时时间到，还没加载到，认为加载失败");
		
		this.notifyPlayerLoadResult(false);
		//this.notifyPlayerLoadInit(false);
	}

	//如果无实际歌曲mp3文件，地址最后为  "/empty.mp3"
	private function checkFileUrl(sUrl : String) : Boolean {
		var c:RegExp=/(empty.mp3)$/i;
		if(c.test(sUrl)){//如果包含"/empty.mp3"则可以马上判断此歌曲为空
			return false;
		}
		return true;
	}

	//对sound添加监听器
	private function addListener_SD() {
		sd.addEventListener(Event.OPEN, openHandler);
		sd.addEventListener(ProgressEvent.PROGRESS, progressHandler);
		sd.addEventListener(Event.COMPLETE, completeHandler);
		sd.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
		sd.addEventListener(Event.ID3, id3Handler);
	}

	private function removeListener_SD() {
		sd.removeEventListener(Event.OPEN, openHandler);
		sd.removeEventListener(ProgressEvent.PROGRESS, progressHandler);
		sd.removeEventListener(Event.COMPLETE, completeHandler);
		sd.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
		sd.removeEventListener(Event.ID3, id3Handler);
	}

	//对soundChannal添加和删除监听器
	private function addListener_SDC() {
		sdc.addEventListener(Event.SOUND_COMPLETE, playCompleteHandler);
	}

	private function removeListener_SDC() {
		if(sdc != null) {
			sdc.removeEventListener(Event.SOUND_COMPLETE, playCompleteHandler);
		}
	}

	private function openHandler(event : Event) : void {
		trace2("xplayer.openHandler");
		//通知listener说第一个字节加载到
		//notifyPlayerLoadInit();
	}

	//
	private function completeHandler(event : Event) : void {
		trace2("xplayer.completeHandler");
		this.notifyPlayerLoadResult(true);
	}

	private function ioErrorHandler(event : Event) : void {
		trace2("xplayer.ioErrorHandler"+event);
		this.notifyPlayerLoadResult(false);
	}

	private function progressHandler(event : ProgressEvent) : void {
		//  T.p("progressHandler: " + event);
		if(getBytesLoaded() > 0) {
			//T.p("检测到下载了第一个字节 sd.duration:"+thisObj.sd.duration+" posi:"+thisObj.sd.position);
				
			//通知listener说第一个字节加载到
			notifyPlayerLoadInit();
				
			sd.removeEventListener(ProgressEvent.PROGRESS, progressHandler);
		}
	}

	//注意如果要访问id3属性是需要跨域策略文件的,
	//除只能于本地交互的东西外所有swf文件都允许从网络来源加载声音和视频,只要不访问它的内部数据即可
	private function id3Handler(event : Event) : void {
       trace2("id3Handler");
       /*
       for(var i:String in sd.id3){
       	trace2(i+" "+sd.id3[i],false);
       }
       //*/
	}

	//--
	private function playCompleteHandler(event : Event) : void {
		trace2("xplayer.playCompleteHandler");
     	
		notifyPlayComplete();
	}

	//
	override public function close() : void {
		trace2("xplayer.close");
		
		//--计时器释放
		if(timer_connecting != null) {
			timer_connecting.stop();//--超时记数要停下
		}
		
		//淡出器要释放
		volFader.clear();
		
		//--先把声音停掉
		pause();
		
		//--
		removeListener_SDC();
		removeListener_SD();
			
		//--T.p("关闭下载中的流");
		try {
			if(this.isLoadOK==false){//~~没加载完的时候要关闭流
				sd.close();//停止流，这个挺重要的
			}
		}catch(er : Error) {
			//trace2("捕获到了异常发生错误，不能关闭:"+er,false);
		}
			
		//sdt=null;
		//sdc=null;
		//sd=null;
	}

	override public function pause() : void {
		trace2("pause");
		
		if(isPlaying2 == false) {
			//如果已经处在暂停状态就不要再暂停
			return;
		}
		
		isPlaying2 = false;
		
		if(sdc != null) {
			//已经启动过播放了
			T.p("sdc.stop!"+sdc.position);
			sdc.stop();
			startPosi = sdc.position;
			if(isNaN(startPosi)) {
				trace2("!!isNaN!!:" + startPosi);
				startPosi = 0;
			}
		}else {
			trace2("sdc为空,posi=0");
			//sdc==null的时候是还没点sd.play()的时候，这个时候声音是静止的
			startPosi = 0;
		}
		trace2("暂停的时间"+startPosi);
	}

	//这个也是表示启动播放 
	override public function resume() : void {
		
		trace2("Resume");
		
		if(isPlaying2) {
			//如果已经处在播放状态就不要再播放
			return;
		}
		isPlaying2 = true;
		
		//--
		try {
			trace2("播放"+startPosi+" "+outerVolume);
			sdc = sd.play(startPosi);
			sdt.volume = outerVolume;
			sdc.soundTransform = sdt;//设置声音音量

			addListener_SDC();
		}catch(err : Error) {
			//这里抛出错误是可能的，比如(http://music.yobo.com/uu/20070913mp3/2254EM.mp3)
			//这个歌曲可以加载下来，但是在win mediaplayer里就说是无效的声音文件
			trace2("resume出现了问题!!", err.message);
			this.notifyPlayerError();
		}
	}

	//停止在开头
	override public function stop() : void {
		trace2("stop");
		pause();
		startPosi = 0;
	}

	//注意这个参数是秒数
	override public function seek(time : Number) : void {
		if(isPlaying2) {
			pause();
			startPosi = time;
			resume();
		}else {
			startPosi = time;
		}
	}

	//----------
	//跟播放池相关的
	override public function getFileUrl() : String {
		//T.p("getFileUrl"+fileUrl);
		return fileUrl;
	}

	
	override public function getState() : int {
		return -1;//这里没有定义getState();
	}

	//
	override public function getBytesLoaded() : int {
		return sd.bytesLoaded;
	}

	override public function getBytesTotal() : int {
		return sd.bytesTotal;
	}

	//现在下载的能播放的声音长度,这个没有多大实际用处
	override public function getLength() : Number {
		return sd.length;
	}

	//下载进度
	override public function getLoading() : Number {
		//T.p("getLoading"+sd+" "+getBytesLoaded()+" "+getBytesTotal());
		if(getBytesTotal() == 0) {
			return 0;
		}
		var p : Number = getBytesLoaded() / getBytesTotal();
		
		return p;
	}

	//
	override public function getPosi() : Number {
		var posi : Number = 0;
		
		if(isPlaying2 == false) {
			//处在暂停状态的时候要返回startPosi.这个是为了处理seek()的时候在isPlayer2=false的时候也能正确显示进度
			posi = startPosi;
		}else {
			//处在播放状态的时候根据情况决定
			if(sdc != null) {
				posi = sdc.position;
			
				if(isNaN(posi)) {
					//还没下载下来呢，要返回0
					trace2("getPosi出现了空的数据" + posi);
					posi = 0;
				}
			}else {
				posi = 0;
			}
		}
		
		return posi;
	}

	//取得总的播放时间长度
	override public function getDur() : Number {
		var dur : Number = sd.length;
		
		if(isNaN(dur)) {
			return 0;
		}
		
		if(this.isLoadOK){//完全下载了就是真正的时间
			
		}else {
			//没下载完毕就用评估的时间
			var loading : Number = getLoading();
			
			if(loading == 0) {
				return 0;
			}else {
				dur = Math.floor(dur / loading);
			}
		}
		
		return dur;
	}

	//取得播放的进度
	override public function getPlaying() : Number {
		//T.p("getPlaying "+sd.length+" "+sdc.position);
		var dur : Number = getDur();
		if(dur == 0) {
			return 0;
		}
		
		var posi : Number = getPosi();
		
		var p : Number = posi / dur;
		
		return p;
	}

	//
	override public function isPlaying() : Boolean {
		return isPlaying2;
	}

	override public function IsPlayComplete() : Boolean {
		return isPlayComplete;
	}

	override public function IsError() : Boolean {
		return isError;
	}

	//
	override public function getVol() : Number {
		//T.p("getVol");
		if(isError || isPlayComplete) {
			//出现了错误则返回声音0,这个是为声音淡出而做的
			//T.p("返回了被",isError,isPlayComplete);
			return 0;
		}
		return outerVolume;
	}

	//设置声音,0~1
	override public function setVol(p_volume : Number) : void {
		//T.p("setVol",p_volume);
		if(isError || isPlayComplete) {
			//出现错误则不允许设置声音了
			//T.p("返回了被",isError,isPlayComplete);
			return;
		}
		outerVolume = p_volume;
		
		sdt.volume = p_volume;
		if(sdc != null) {
			sdc.soundTransform = sdt;
		}
	}

	//------声音变淡------
	//注意考虑到在我淡出的时候收到pasue()命令的情况，这个情况是可能的，因为EI外部会调用我
	override public function startFadeout() : void {
		trace2("启动声音淡出");
		volFader.start(0);
	}

	//声音淡入，从0~指定目标
	override public function startFadeIn(p_aimVol : Number) : void {
		trace2("启动声音淡入");
		setVol(0);
		volFader.start(p_aimVol);
	}

	override public function addListener(pl : IXPlayerListener) : void {
		//T.p3("Mp3Player:"+getName()+" 添加了一个lisntern");
		var i : int;
		var len : int = aListener.length;
		for(i = 0;i < len;i++) {
			if(aListener[i] == pl) {
				//T.p3("有相同的就不再添加了");
				return ;
			}
		}
		
		aListener.push(pl);
		
		//把在addListenr之前发生的事件广播给此listenr
		//T.p3("添加后分发");
		len = aMsg.length;
		for(i = 0;i < len;i++) {
			var msg : String = aMsg[i];
			switch(msg) {
				case "OnPlayerLoadResult_true":
					//T.p("添加listerner后分发: loadResult:true");
					pl.OnPlayerLoadResult(true);
					break;
				case "OnPlayerLoadResult_false":
					//T.p("添加listerner后分发: loadResult:false");
					pl.OnPlayerLoadResult(false);
					break;
				case "OnPlayerLoadInit":
					//T.p("添加listerner后分发: loadInit");
					pl.OnPlayerLoadInit();
					break;
				
				case "OnPlayComplete":
					//这个其实在SmartPlayerPoll里是用不到的
					//T.p("添加listerner后分发: playComplete");
					pl.OnPlayComplete();
					break;
				case "OnPlayerError":
					//sdc.play()出现了错误
					//T.p("添加listerner后分发: OnPlayerError");
					pl.OnPlayerError();
					break;
			}
		}
	}

	private function notifyPlayerLoadResult(success : Boolean) : void {
		//T.p("xplayer_________notifyPlayerLoadResult",success);
		
		//--计时器释放
		if(timer_connecting!=null){
			timer_connecting.stop();//超时记数要停下
		}
		
		if(success == false) {
			//T.p("加载失败",fileUrl);
			isError = true;
			
			close();
		}else {
			isLoadOK = true;
		}
		
		//T.p3("notifyPlayerLoadResult:"+success+" "+getName());
		doDispatcheEvent("OnPlayerLoadResult_" + success);
	}

	private function notifyPlayerLoadInit() : void {
		//T.p3("notifyPlayerLoadInit:"+getName());
		//--超时记数要停下
		timer_connecting.stop();
		
		if(isPlaying2) {
			//T.p("下载第一字节后启动播放");	
			this.resume();
		}else {
			//T.p("下载第一字节后暂停播放");
			this.pause();		
		}
		
		doDispatcheEvent("OnPlayerLoadInit");
	}

	//!!这个是很隐秘的东西，有一种可能是歌曲可以下载不出错误，但是sdc.play()的时候会出现错误
	private function notifyPlayerError() {
		trace2("notifyPlayerError!!");
		isError = true;
		close();
		 	
		doDispatcheEvent("OnPlayerError");
	}

	private function notifyPlayComplete() : void {
		isPlayComplete = true;
		//T.p3("notifyPlayComplete:"+getName());
		doDispatcheEvent("OnPlayComplete");
	}
	
	//执行发送事件的逻辑,这个逻辑是如果没有监听器则吧自己的事件暂存起来
	private function doDispatcheEvent(sEventName : String) {
		var len : int = aListener.length;
		if(len == 0) {
			//在没有添加listener之前就要把消息缓存
			aMsg.push(sEventName);//加入消息队列
		}else {
			//对自己的各个listener广播消息
			for(var i : int = 0;i < len;i++) {
				var l : IXPlayerListener = aListener[i];
					
				//发出各个事件
				switch(sEventName) {
					case "OnPlayComplete":
						l.OnPlayComplete();
						break;
					case "OnPlayerError":
						l.OnPlayerError();
						break;
					case "OnPlayerLoadInit":
						l.OnPlayerLoadInit();
						break;
					case "OnPlayerLoadResult_true":
						l.OnPlayerLoadResult(true);
						break;
					case "OnPlayerLoadResult_false":
						l.OnPlayerLoadResult(false);
						break;
				}
			}
		}
	}

	//实现*****************IXPlayerListener的接口*************************
	override  public function OnPlayComplete() {
	}

	override public function OnPlayerLoadResult(success : Boolean) {
		//T.p("MP3_XPlayer.OnPlayerLoadResult:");
		//被通知到上一首歌曲加载完毕,不需要额外的 pause函数，因为pause会有隐藏的故障
		//这个会根据当前自己是否被调用了pause而自动做
		//T.p3("启动预先加载:"+getName());
		startLoad();//被通知说自己的上一个加载完毕，不管成功还是失败，都说加载完了
	}

	override public function OnPlayerLoadInit() { 
	}

	override public function OnPlayerError() { 
	}

	//======================调试信息
	public function trace2(sTip : String,bTitle:Boolean=false) {
		//*
		if(bTitle){//着重显示的标题信息
			T.p(id + " ___________________" + sTip + "_____________________");
			T.p("id:" + id, "fileUrl:" + fileUrl);
		}else{//常规信息
			T.p(sTip);
		}
		//*/
	}
} //end of class
}